﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.SceneGraph;

namespace Daybreak.Core.Rendering
{
    /// <summary>
    /// Pure abstract base class for drawable geometry. 
    /// </summary>
    /// <remarks>
    /// Everything that can be rendered is implemented as a class derived from Drawable. 
    /// The Drawable class contains no methods for assembling primitives, 
    /// since these are provided by subclasses such as MeshPartInstance.
    /// </remarks>
    public abstract class Drawable
    {
        #region nested types

        /// <summary>
        /// Groups drawables in one container
        /// </summary>
        public abstract class Container : IRenderable
        {
            public Drawable[] Parts;
            public Scene.Material.Binding[] Bindings;

            public abstract void MergeBounding(ref BoundingBox result);

            public abstract void CacheNodeTransform(Transform transformNode, ref Matrix transformMatrix);

            public unsafe virtual void CopyControlVerticesTo(ref float* rawVertexData, int vIdx)
            {
            }

            public abstract void GetGeometryData(RenderItem slot, Drawable item);
            public abstract void GetWorldTransforms(List<Matrix> matrices);
            public abstract IMaterial GetMaterial(Drawable item);


            #region IRenderable Members

            public void RenderSelf(IRender render, ref RenderArgs args)
            {
                for (int n = 0; n < Parts.Length; n++)
                {
                    Drawable item = Parts[n];

                    if (item == null)
                        continue;

                    RenderItem dip = render.RenderQueue.AllocateSlot<RenderItem>();

                    item.GetGeometryData(dip, this);

                    dip.WorldBoundingVolume = args.BoundingBox;
                    dip.CacheNodeTransform(args.TransformNode, ref args.TransformMatrix);

                    if (render.Debug)
                    {
                        BBoxItem bb = render.RenderQueue.AllocateSlot<BBoxItem>();

                        bb.Material = Scene.Material.Debug;
                        bb.BoundingVolume = args.BoundingBox;

                    }

                }
            }

            #endregion
        }

        public abstract class Topology
        {
            public struct Input
            {
                public VertexElementUsage Semantic;
                public int Set;
                /// <summary>
                /// Vertex element size in floats
                /// </summary>
                public int Size;

                public void SetUsage<T>(VertexElementUsage sem, int set) where T : struct, IEquatable<T>
                {
                    Semantic = sem;
                    Set = set;
                    Size = VertexArray.Generic<T>.ItemSizeFloat;
                }

                public VertexElement CreateVertexElement(int stream, int byteOffset, byte usgeIndex)
                {
                    return new VertexElement((short)stream, (short)byteOffset, Format, VertexElementMethod.Default, Semantic, usgeIndex);
                }

                public VertexElementFormat Format
                {
                    get
                    {
                        switch (Semantic)
                        {
                            case VertexElementUsage.Position:
                            case VertexElementUsage.Normal:
                            case VertexElementUsage.Binormal:
                            case VertexElementUsage.Tangent:

                                return VertexElementFormat.Vector3;

                            case VertexElementUsage.TextureCoordinate:

                                return VertexElementFormat.Vector2;

                            case VertexElementUsage.Color:

                                return VertexElementFormat.Color;

                            case VertexElementUsage.BlendIndices:

                                return FormatBySize;

                            case VertexElementUsage.BlendWeight:

                                return FormatBySize;

                            default: return VertexElementFormat.Unused;
                        }
                    }
                }

                public VertexElementFormat FormatBySize
                {
                    get
                    {
                        switch (Size)
                        {

                            case 1:
                                return VertexElementFormat.Single;

                            case 2:
                                return VertexElementFormat.Vector2;

                            case 3:
                                return VertexElementFormat.Vector3;

                            case 4:
                                return VertexElementFormat.Vector4;

                            default:
                                return VertexElementFormat.Unused;
                        }
                    }
                }

            }

            public struct InputOffset
            {
                public Input Item;
                public int StartOffset;

                public void SetUsage<T>(VertexElementUsage sem, int offset, int set) where T : struct, IEquatable<T>
                {
                    Item.SetUsage<T>(sem, set);

                    StartOffset = offset;
                }

                public override string ToString()
                {
                    return string.Format("Vector{0}:{1} +{2} (set:{3})", Item.Size, Item.Semantic, StartOffset, Item.Set);
                }

            }

            protected InputOffset[] _inputs;
            protected int[] _vertexChannelOffset;

            public int[] IndexArray;
            private CullMode _windingOrder = CullMode.CullClockwiseFace;

            public Topology() { }

            public Topology(int[] arr)
            {
                IndexArray = arr;
            }

            public void SetTopologyMap(InputOffset[] inputArr, int[] indexArr)
            {
                Inputs = inputArr;

                int cnt = InputCount;
                int offset = 0;

                IndexArray = new int[cnt * indexArr.Length];

                for (int i = 0; i < indexArr.Length; i++)
                {
                    for (int n = 0; n < cnt; n++)
                        IndexArray[offset++] = indexArr[i];
                }

            }

            public VertexElement[] GetVertexElements(int bonePerVertex)
            {
                int[] offsetArr = VertexChannelOffsets;
                int count = offsetArr.Length;// InputCount;

                if (count == 0)
                    return null;

                if (bonePerVertex > 4)
                    bonePerVertex = 4;

                VertexElement[] result = null;

                if (bonePerVertex > 0)
                    result = new VertexElement[count + 2];
                else
                    result = new VertexElement[count];

                int offset = 0;
                int i = 0;

                for (i = 0; i < count; i++)
                {
                    result[i] = Inputs[offsetArr[i]].Item.CreateVertexElement(0, offset, 0);

                    offset += (Inputs[i].Item.Size * 4);
                }

                if (bonePerVertex == 0)
                    return result;

                // add skinning info

                if (bonePerVertex == 4)
                {
                    result[i] = CreateElement(offset, VertexElementFormat.Vector4, VertexElementUsage.BlendIndices);
                    result[i + 1] = CreateElement(offset + 16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight);

                    return result;
                }

                if (bonePerVertex == 3)
                {
                    result[i] = CreateElement(offset, VertexElementFormat.Vector3, VertexElementUsage.BlendIndices);
                    result[i + 1] = CreateElement(offset + 12, VertexElementFormat.Vector3, VertexElementUsage.BlendWeight);

                    return result;

                }

                if (bonePerVertex == 2)
                {
                    result[i] = CreateElement(offset, VertexElementFormat.Vector2, VertexElementUsage.BlendIndices);
                    result[i + 1] = CreateElement(offset + 8, VertexElementFormat.Vector2, VertexElementUsage.BlendWeight);

                    return result;

                }

                // bonePerVertex == 1

                result[i] = CreateElement(offset, VertexElementFormat.Single, VertexElementUsage.BlendIndices);
                result[i + 1] = CreateElement(offset + 4, VertexElementFormat.Single, VertexElementUsage.BlendWeight);

                return result;

            }

            private VertexElement CreateElement(int offset, VertexElementFormat format, VertexElementUsage usage)
            {
                return new VertexElement(0, (short)offset, format, VertexElementMethod.Default, usage, 0);
            }

            internal unsafe int BuildIndexData(ref int* rawIndexData, int startVertex)
            {
                int vCount = VertexIndexCount;
                int len = startVertex + vCount;

                int* ptr = rawIndexData;

                for (int i = startVertex; i < len; i++)
                    *rawIndexData++ = i;

                if (_windingOrder == CullMode.CullClockwiseFace)
                    SwapIndexOrder(ptr, vCount - 2); // we need CullCounterClockwiseFace winding order

                return vCount;

            }

            private unsafe void SwapIndexOrder(int* ptr, int count)
            {
                for (int i = 0; i < count; i+=3)
                {
                    int tmp = ptr[i];
                    ptr[i] = ptr[i + 2];
                    ptr[i + 2] = tmp;
                }
            }

            internal unsafe void BuildVertexData(ref float* rawVertexData, Mesh.VertexStream vStream, Drawable.Container container)
            {
                int vCount = this.VertexIndexCount;
                int iCount = this.InputCount;
                int stride = this.IndexStride;
                int idxOffset = 0;

                int[] offsetArr = VertexChannelOffsets;

                VertexArray[] arrList = new VertexArray[offsetArr.Length];
                int* offsetPtr = stackalloc int[offsetArr.Length];
                int vChannelCounter = 0;

                // bind input sets first
                for (int i = 0; i < offsetArr.Length; i++)
                {
                    VertexArray arr = vStream.GetChannel(Inputs[offsetArr[i]].Item.Semantic);
                    offsetPtr[vChannelCounter] = Inputs[offsetArr[i]].StartOffset;
                    arrList[vChannelCounter++] = arr;

                    arr.SetCurrentSet(Inputs[offsetArr[i]].Item.Set);
                }

                for (int v = 0; v < vCount; v++)
                {
                    for (int i = 0; i < vChannelCounter; i++)
                    {
                        int vIndex = this.IndexArray[idxOffset + offsetPtr[i]];

                        arrList[i].CopyVertexTo(ref rawVertexData, vIndex);
                    }

                    // get position channel vertex index
                    int pIdx = this.IndexArray[idxOffset + offsetPtr[0]];

                    if (container != null)
                        container.CopyControlVerticesTo(ref rawVertexData, pIdx);

                    idxOffset += stride;
                }
            }

            public void CorrectAxis(IAxisCorrector corrector)
            {
                if (corrector == null)
                    return;

                corrector.CorrectTopology(this);
            }

            public InputOffset[] Inputs
            {
                get { return _inputs; }
                set
                {
                    _inputs = value;
                    _vertexChannelOffset = null;
                }
            }

            public int[] VertexChannelOffsets
            {
                get
                {
                    if (_vertexChannelOffset == null)
                        _vertexChannelOffset = AssembleVertexChannels();

                    return _vertexChannelOffset;
                }

                set
                {
                    if (value == null)
                        _vertexChannelOffset = null;
                }
            }

            private int[] AssembleVertexChannels()
            {
                if (_inputs == null)
                    return null;

                unsafe
                {
                    int iCount = this.InputCount;
                    bool* ptr = stackalloc bool[16];
                    int* idxPtr = stackalloc int[iCount];

                    int cnt = 0;

                    for (int i = 0; i < iCount; i++)
                    {
                        if (!ptr[(int)_inputs[i].Item.Semantic])
                        {
                            ptr[(int)_inputs[i].Item.Semantic] = true;
                            idxPtr[cnt++] = i;
                        }
                    }

                    int[] result = new int[cnt];

                    for (int i = 0; i < result.Length; i++)
                        result[i] = idxPtr[i];

                    return result;
                }

            }

            public override string ToString()
            {
                return string.Format("{0} count:{1}", GetType().Name, PrimitiveCount);
            }

            /// <summary>
            /// Defines how data in a vertex stream is interpreted (primitive topology)
            /// </summary>
            public abstract PrimitiveType Type { get; }

            public abstract int PrimitiveCount { get; }

            public int VertexIndexCount
            {
                get
                {
                    int stride = IndexStride;

                    if (stride == 0)
                        return 0;

                    return IndexCount / stride;
                }
            }

            public int IndexStride
            {
                get
                {
                    if (Inputs == null)
                        return 0;

                    int stride = 0;

                    for (int i = 0; i < Inputs.Length; i++)
                        if (Inputs[i].StartOffset > stride)
                            stride = Inputs[i].StartOffset;

                    return stride + 1;
                }
            }

            public int FloatsPerVertex
            {
                get
                {
                    if (Inputs == null)
                        return 0;

                    int result = 0;

                    int[] offset = VertexChannelOffsets;

                    for (int i = 0; i < offset.Length; i++)
                        result += Inputs[offset[i]].Item.Size;

                    return result;
                }
            }

            public int InputCount
            {
                get
                {
                    if (Inputs == null)
                        return 0;

                    return Inputs.Length;
                }
            }

            public int IndexCount
            {
                get
                {
                    if (IndexArray == null)
                        return 0;

                    return IndexArray.Length;
                }
            }

            /// <summary>
            /// Defines winding order that may be used to identify back faces.
            /// </summary>
            public CullMode WindingOrder
            {
                get { return _windingOrder; }
                set { _windingOrder = value; }
            }

        }

        /// <summary>
        /// The triangles class provides the information needed to bind vertex attributes together 
        /// and then organize those vertices into individual triangles.
        /// Each triangle described by the mesh has three vertices. 
        /// The first triangle is formed from the first, second, and third vertices. 
        /// The second triangle is formed from the fourth, fifth, and sixth vertices, and so on. 
        /// </summary>
        public class Triangles : Topology
        {
            public Triangles() { }

            public Triangles(int[] indArr)
                : base(indArr)
            {
            }

            public override int PrimitiveCount
            {
                get { return VertexIndexCount / 3; }
            }

            public override PrimitiveType Type
            {
                get { return PrimitiveType.TriangleList; }
            }
        }

        #endregion

        /// <summary>
        /// Default material
        /// </summary>
        protected IMaterial _material;

        public abstract void GetGeometryData(RenderItem slot, Drawable.Container container);

        public override string ToString()
        {
            return string.Format("Solid:{1} count:{2}", TopologyType, PrimitiveCount);
        }

        public virtual PrimitiveType TopologyType
        {
            get { return PrimitiveType.TriangleList; }
        }

        public abstract int PrimitiveCount { get; }


        public IMaterial Material
        {
            get { return _material; }
        }

    }

    public interface IRenderable
    {
        void RenderSelf(IRender render, ref RenderArgs args);
    }
}
